
#
# The file is used to express the object format inside the core.
#
# Because the object is the one can be expanded easily, it's better to use
# a single Python package to encapuslate the interface to indiacte the
# the specified version of the objects - it can also be stored with atr
# format.
#

import json
import os
import time
import ab_exceptions
import ab_file

import ab_utils as utils

from ab_field import AtrField, AtrFields

__version__ = '3.2'


class AtrAttribution:

    Default = 0x00
    Extended = 0x01
    Extractable = 0x02
    Picture = 0x04
    Standard = 0x10
    Simple = 0x20
    Simplest = 0x40

    No_Update = 0x008000
    No_SourceWindow = 0x010000

    MAP = {
        'default': Default,
        'extractable': Extractable,
        'picture': Picture,
        'standard': Standard,
        'simple': Simple,
        'simplest': Simplest,
        'no_update': No_Update,
        'no_window': No_SourceWindow
    }

    def __init__(self, attribute=Default):
        self.attribute = self.convert(attribute)

    def __repr__(self):
        return self.dump(json=True)

    def __eq__(self, obj):
        return isinstance(obj, AtrAttribution) and \
            self.attribute == obj.attribute

    def convert(self, attribute, reverse=False):
        if reverse:
            ret = ''
            default = ''
            for k, v in AtrAttribution.MAP.items():
                if v == AtrAttribution.Default:
                    default = k

                if self.attribute & v != 0:
                    ret += ',%s' % k

            if len(ret) > 0:
                ret = ret[1:]

            if len(ret) == 0:
                ret = default

            return ret
        else:
            if isinstance(attribute, (str, unicode)):
                attr = AtrAttribution.Default
                for b in attribute.lower().split(','):
                    a = b.strip()
                    if a in AtrAttribution.MAP:
                        attr |= AtrAttribution.MAP[a]
                    else:
                        reversed = 'no_%s' % a
                        if reversed in AtrAttribution.MAP:
                            attr &= ~AtrAttribution.MAP[reversed]
            elif isinstance(attribute, (int, long)) and attribute != -1:
                attr = attribute
            elif isinstance(attribute, AtrAttribution):
                attr = attribute.get()
            else:
                attr = AtrAttribution.Default

            return attr

    def update(self, attribute):
        self.attribute |= self.convert(attribute)

    def set(self, attribute):
        self.attribute = self.convert(attribute)

    def get(self):
        return self.attribute

    def dump(self, json=False):
        if json:
            return self.convert(self.attribute, reverse=True)
        else:
            return '0x%02x' % (self.attribute or AtrAttribution.Default)


class AtrObject:
    # Match mode
    MatchDefault = 0x00
    # the last two bits
    MatchPrecise = 0x01
    MatchRecur = 0x00
    MatchMask = 0x03

    MatchAll = 0x10

    def __init__(self, subject='', content='', field=None,
                 node=None, attribute=0, opaque=None, root=True,
                 plxname=None):
        self.node = None
        self.root = root
        self.subject = subject
        self.content = content
        self.plxname = plxname

        self.opaque = opaque

        self.children = None
        self.attribute, self.fields = self.__legacyLayout(attribute, field)

        self.appendChildren(node)

    def __iadd__(self, obj):
        self.append(obj)

        return self

    def __len__(self):
        return len(self.node or list())

    def __repr__(self):
        return '<AtrObject> subject (%s) content (%s) fields (%s)' \
            'node (%d) attribute(%s) children(%d) plxname(%s)' % (
                self.subject, self.content, str(self.fields),
                len(self.node or list()), str(self.attribute),
                len(self.children or list()), self.plxname or '')

    def __eq__(self, obj):
        ret = isinstance(obj, AtrObject)
        if ret:
            ret = self.subject == obj.subject
            if not ret:
                ret = utils.safestr(self.subject) == utils.safestr(obj.subject)
        if ret:
            ret = self.content == obj.content
            if not ret:
                ret = utils.safestr(self.content) == utils.safestr(obj.content)

        if ret:
            ret = self.fields == obj.fields

        if ret:
            if self.children is not None and obj.children is not None:
                ret = len(self.children) == len(obj.children)
                if ret:
                    for k, c in enumerate(self.children):
                        ret = c == obj.children[k]
                        if not ret:
                            break
            elif not (self.children is None and obj.children is None):
                ret = False

        if ret:
            if self.node is not None and obj.node is not None:
                ret = len(self.node) == len(obj.node)
                if ret:
                    for k, c in enumerate(self.node):
                        ret = c == obj.node[k]
                        if not ret:
                            break
            elif not (self.node is None and obj.node is None):
                ret = False

        return ret

    def __legacyLayout(self, attribute, fd):
        if fd is None:
            fd = list()

        if isinstance(fd, AtrField):
            return AtrAttribution(attribute), AtrFields(fd)
        elif isinstance(fd, (list, tuple)) and len(fd) > 0:
            # check the types in the list
            for f in fd:
                if not isinstance(f, AtrField):
                    break
            else:
                return AtrAttribution(attribute), AtrFields(fd)

        elif len(fd) > 2:
            return AtrAttribution(fd[0]), AtrFields(AtrField(fd[1], fd[2]))
        else:
            return AtrAttribution(attribute), AtrFields()

    def isEmpty(self):
        return len(self.node or list()) == 0 \
            and len(self.children or list()) == 0

    def isUpdatable(self):
        return self.attribute and \
            (self.attribute.get() & AtrAttribution.No_Update) == 0

    def adjust(self, start=-1, size=-1, recursive=True):
        if start != -1:
            self.fields.adjust(start)

            if recursive and self.node is not None:
                for node in self.node:
                    node.adjust(start, size, recursive)
            if recursive and self.children is not None:
                for child in self.children:
                    child.adjust(start, size, recursive)

        if size != -1:
            # update the first field
            if len(self.fields):
                self.fields[0].adjust(size=size)

    def append(self, objs):
        if self.root:
            return self.appendChildren(objs)
        elif not isinstance(objs, AtrObject):
            return False
        elif self.node is None:
            self.node = list()

        # object.subject = None means it's anonymous object to contain nodes
        if objs.subject is None:
            if objs.node is not None and isinstance(objs.node, (list, tuple)):
                self.node.extend(objs.node)
        else:
            self.node.append(objs)

        return True

    def getItems(self):
        return self.node or list()

    def appendChildren(self, objects, remove=False):
        if remove:
            self.children = None

        if isinstance(objects, (list, tuple)):
            for obj in objects:
                ret = self.appendChildren(obj)
                if not ret:
                    return False
            return True
        elif not isinstance(objects, AtrObject):
            return False

        if self.children is None:
            self.children = list()

        self.children.append(objects)
        return True

    def extractable(self):
        return (self.attribute.get() & AtrAttribution.Extractable) \
            == AtrAttribution.Extractable or len(self.children or list()) > 0

    def extract(self, expression=None):
        if expression is None:
            expression = 'C'

        listp = list()
        for e in expression:
            if e == 's':
                listp.append(self.subject)
            elif e == 'c':
                listp.append(self.content)
            elif e == 'e':
                listp.append(len(self.children or list()) > 0)
            elif e == 'v':
                listp.append(repr(self.attribute))
            elif e == 'o':
                if len(self.fields) > 1:
                    listp.append('multiple')
                else:
                    listp.append(self.getStart())
            elif e == 'l':
                listp.append(self.getSize())
            elif e == 'C':
                listp.append(self.children)
            elif e == 'F':
                listp.append(self.fields.getFields())
            elif e == 'Q':
                listp.append(self.opaque)
            elif e == 'p':
                listp.append(self.plxname)

        if len(expression) == 1 and len(listp) == 1:
            return listp[0]
        else:
            return listp

    def getField(self):
        return self.fields.getFields()

    def getFields(self):
        return self.fields

    def getStart(self):
        return self.fields.getStart()

    def getSize(self):
        return self.fields.getSize()

    def getAttribution(self):
        return self.attribute

    def hasChildren(self):
        return len(self.children or list()) > 0

    def getChildren(self):
        return self.children or list()

    def update(self, subject=None, content=None,
               attribute=-1, opaque=None, field=None, plxname=None):
        if subject is not None:
            self.subject = subject

        if content is not None:
            self.content = content

        if attribute != -1:
            self.attribute = AtrAttribution(attribute)

        if opaque is not None:
            self.opaque = opaque

        if plxname is not None:
            self.plxname = plxname

        if isinstance(field, (AtrField, list, tuple)):
            self.fields = AtrFields(field)

    # TODO: remove the interface
    def updateLegacy(self, listp, plxname=None):
        if not isinstance(listp, (list, tuple)) or len(listp) == 0:
            return False

        def __updateLegacy(obj, fmts):
            listp = list()
            try:
                for o in fmts:
                    so = AtrObject(o[0], o[1], o[2], plxname=plxname)

                    if o[4] is None or (isinstance(o[4], (list, tuple))
                                        and len(o[4]) == 0):
                        pass
                    else:
                        nodes = __updateLegacy(so, o[4])
                        so.appendChildren(nodes)

                    # do need to support version 2?
                    listp.append(so)
            except:
                pass

            return listp

        try:
            fmts = __updateLegacy(self, listp)
        except:
            fmts = list()

        if len(fmts) > 0:
            self.append(fmts)

    def findWithField(self, field, mode=MatchDefault, level=0):
        opaque = None

        if self.fields.inside(field):
            opaque = self

        if ((mode & self.MatchRecur) == self.MatchRecur
                and opaque is not None) or \
                (mode & self.MatchPrecise) == self.MatchPrecise:
            if self.extractable():
                for obj in self.extract() or list():
                    op = obj.findWithField(field, mode, level+1)
                    if op is not None:
                        opaque = op

        return opaque

    def getLastObject(self, recursive=True):
        objp = self

        if len(objp.children or list()) > 0:
            objp = self.children[-1]

        if len(objp.node or list()) > 0:
            objp = self.node[-1]

        if objp is self:
            return self
        elif recursive:
            return objp.getLastObject()
        else:
            return objp

    def printf(self, extended=True, recurse=True, tag=None, level=0):
        ret = ''
        if tag is not None:
            ret = tag(level)

        ret += '[%d] Subject(%s) Content(%s) Attribute (%s) ' \
            'Node (%d) Children (%d) PlxName (%s)' % (
                level, self.subject, self.content,
                self.attribute, len(self.node or list()),
                len(self.children or list()), self.plxname or '')

        ret += os.linesep

        if extended:
            for fd in self.fields.getFields():
                if tag:
                    ret += tag(level)
                ret += fd.dump() + os.linesep

        if recurse:
            for child in self.children or list():
                ret += child.printf(extended, recurse, tag, level + 1)

        for node in self.node or list():
            ret += node.printf(extended, recurse, tag, level)

        return ret

    @staticmethod
    def loadJson(jfile):
        def _buildAtrObject(jso):
            obj = AtrObject()
            if 'subject' in jso:
                obj.update(subject=jso['subject'])
            if 'content' in jso:
                obj.update(content=jso['content'])
            if 'attribute' in jso:
                obj.update(attribute=jso['attribute'])
            if 'plxname' in jso:
                obj.update(plxname=jso['plxname'])

            if 'field' in jso:
                field = list()
                for fd in jso['field'] or list():
                    field.append(AtrField(*fd))
                obj.update(field=field)
            if 'children' in jso:
                for c in jso['children']:
                    obj.appendChildren(_buildAtrObject(c))
            if 'node' in jso:
                for n in jso['node']:
                    obj.append(_buildAtrObject(n))

            return obj

        with utils.openfile(jfile, 'r') as fd:
            jso = json.load(fd)
            return _buildAtrObject(jso)

        return None

    def json(self, extended=True, recurse=True):

        def _skipEmpty(args, key, value):
            if isinstance(value, (str, unicode)):
                if value != '':
                    args[key] = utils.safestr(value)
            elif value is not None:
                args[key] = value

        ret = dict()
        _skipEmpty(ret, 'subject', self.subject)
        _skipEmpty(ret, 'content', self.content)
        _skipEmpty(ret, 'plxname', self.plxname)
        _skipEmpty(ret, 'attribute', self.attribute.dump(json=True))
        if extended:
            fields = self.fields.dump(json=True)
            _skipEmpty(ret, 'field', fields)

        if recurse and len(self.children or list()) > 0:
            children = list()
            for child in self.children or list():
                children.append(child.json(extended, recurse))
            _skipEmpty(ret, 'children', children)

        if len(self.node or list()) > 0:
            nodes = list()
            for node in self.node or list():
                nodes.append(node.json(extended, recurse))
            _skipEmpty(ret, 'node', nodes)

        return ret

    def dump(self, fname=None, dumpjson=False):
        if dumpjson:
            if fname is None:
                return json.dumps(self.json(), indent=2)
            else:
                with utils.openfile(fname, 'w') as fd:
                    return json.dump(self.json(), fd, indent=2)
        else:
            return self.printf(lambda v: '  ' * v)


class AtrObjectFormat:
    def __init__(self, app, fcv=None, endian=True, plxname=None):
        self.app = app
        self.plxlist = list()

        # little-endian uses 'True'
        self.endian = endian
        self.attribute = AtrAttribution.Default

        if plxname is not None:
            self.plxlist.append(plxname)

        if fcv is None:
            self.opaque = AtrObject(plxname=plxname)
        else:
            self.opaque = AtrObject(os.path.basename(fcv.getFilename()),
                                    field=AtrField(0, len(fcv)),
                                    plxname=plxname)

    def __repr__(self):
        def _indent(level):
            return '  ' * level

        rets = '<<AtrObjectFormat>' + os.linesep
        rets += ' list: %s>' % self.opaque.printf(True, True, tag=_indent)

        return rets

    def __len__(self):
        return len(self.opaque)

    def _getLastPlxName(self):
        if len(self.plxlist) > 0:
            return self.plxlist[-1]
        else:
            return None

    def getEntry(self):
        return self.opaque

    def newFile(self, **kws):
        return ab_file.File(self.app, None, 'rb', **kws)

    def newFieldObject(self, start, size):
        return AtrField(start, size)

    def newItemObject(self, subject='', content='', field=list(),
                      node=None, attribute=0, opaque=None):
        return AtrObject(subject, content, field, node,
                         attribute, opaque, root=False,
                         plxname=self._getLastPlxName())

    def newAttributionObject(self, attribute):
        return AtrAttribution(attribute)

    def consolidateFieldObject(self, subject='', content='', start=0, size=0,
                               node=None, attribute=0, opaque=None):
        return self.newItemObject(subject, content,
                                  self.newFieldObject(start, size),
                                  node, attribute, opaque)

    def getEndian(self, args=None):
        if args:
            return args.get('_endian', self.endian)

        return self.endian

    def setEndian(self, endian, args=None):
        if isinstance(endian, bool):
            self.endian = endian
        elif isinstance(endian, (str, unicode)):
            self.endian = endian.lower().find('little') > -1

        if args:
            args['_endian'] = self.endian

    def setAttribute(self, attribute):
        ret = False
        if isinstance(attribute, AtrAttribution):
            ret = True
            self.attribute = attribute

        return ret

    def notifyProgress(self, args, info):
        '''Depends on the implementation of the parameter
        'notify' to execute'''
        if args.get('c') is not None:
            size = args.get('l') or 1
            args['c'](100 * args['offset'] / size, info)

    def quotedString(self, str_, char=''):
        quoted = 0

        for k, c in enumerate(str_):
            if ord(c) == 0 or c == char:
                quoted = k
                break

        if quoted == 0:
            return str_
        else:
            return str_[:quoted]

    def readFile(self, afile, size, sta=-1, off=-1, **args):
        if off == -1:
            off = args.get('offset', 0)
        if sta == -1:
            off += args.get('start', 0)

        return afile.read(size, off)

    def readValue(self, afile, size, off=-1, **args):
        if size == -1:
            size = 4

        z = self.readFile(afile, size, -1, off, **args)
        if '_endian' not in args:
            args['_endian'] = self.endian

        return utils.atoi(z, **args)

    def readVariableValue(self, afile, sta=-1, off=-1, **args):
        if off == -1:
            off = args.get('offset', 0)
        if sta == -1:
            off += args.get('start', 0)

        ret, k, cont = 0, 0, True
        while cont:
            ch = ord(afile.read(1, off + k))
            k += 1

            cont = (ch & 0x80) == 0x80
            ret = ret * 128 + (ch & 0x7f)

        return k, ret

    def readString(self, afile, size=-1, sta=-1, off=-1, **args):
        ret = ''

        if size < 0:
            tmp = len(afile)
        else:
            tmp = min(1000, size)

        if off == -1:
            off = args.get('offset', 0)
        if sta == -1:
            off += args.get('start', 0)

        zero = False
        while size != 0 and not zero:
            z = afile.read(tmp, off)
            for c in z:
                if ord(c) != 0:
                    ret += c
                else:
                    zero = True
                    break

            off += len(z)
            if len(ret) == len(z) == tmp and tmp < size:
                size -= tmp
                tmp = min(1000, size)
            else:
                break

        return ret


# refers to the document to know the full design of the class
class AtrObjectFormatEx(AtrObjectFormat):
    _TYPE_NONE = 0
    _TYPE_DEBUG = 1 << 0  # enable DEBUG
    _TYPE_IGNORE = 1 << 1
    _TYPE_RECURSIVE = 1 << 2
    _TYPE_CUSTOMIZE = 1 << 3
    _TYPE_NEED_VERIFY = 1 << 4
    _TYPE_IGN_CONTENT = 1 << 5  # Ignore if no content
    _TYPE_UPDATE_CONTENT = 1 << 6
    _TYPE_EXPORT = 1 << 7
    _TYPE_EXPORT_OBJ = 1 << 8
    _TYPE_EVAL = 1 << 9
    _TYPE_VARIANT = 1 << 10
    _TYPE_FUNCTION = 1 << 11
    _TYPE_VALUE = 1 << 12
    _TYPE_SHORT_VALUE = 1 << 13
    _TYPE_DECIMAL = 1 << 14
    _TYPE_STRING = 1 << 15
    _TYPE_TIMESTAMP = 1 << 16
    _TYPE_ENUM = 1 << 17
    _TYPE_BOOLEAN = 1 << 18
    _TYPE_OBJECT = 1 << 19
    _TYPE_BITS = 1 << 20

    VERSION = __version__

    _TYPE_MAPS = {
        'none': _TYPE_NONE,
        'debug': _TYPE_DEBUG,
        'ignore': _TYPE_IGNORE,
        'recursive': _TYPE_RECURSIVE,
        'verify': _TYPE_NEED_VERIFY,
        'update_content': _TYPE_UPDATE_CONTENT,
        'ignore_content': _TYPE_IGN_CONTENT,
        'export': _TYPE_EXPORT,
        'objexp': _TYPE_EXPORT_OBJ,
        'eval': _TYPE_EVAL,
        'variable': _TYPE_VARIANT,
        'value': _TYPE_VALUE,
        'svalue': _TYPE_SHORT_VALUE,
        'decimal': _TYPE_DECIMAL,
        'string': _TYPE_STRING,
        'timestamp': _TYPE_TIMESTAMP,
        'enum': _TYPE_ENUM,
        'bool': _TYPE_BOOLEAN,
        'object': _TYPE_OBJECT,
        'bits': _TYPE_BITS
    }

    def __init__(self, app, fcv=None, endian=True, plxname=None):
        AtrObjectFormat.__init__(self, app, fcv, endian, plxname)

    def setCurrentPlx(self, plxname):
        self.plxlist.append(plxname)

    def resetLastPlx(self):
        self.plxlist.pop(-1)

    def updateLegacy(self, atrFmt):
        self.opaque.updateLegacy(atrFmt, plxname=self._getLastPlxName())

    def _parse(self, subject, descriptor, afile, start, off,
               exported, debug=False, **kws):
        def __pt(debug, prompt, extra=''):
            if not debug:
                return

            print ' __pt:', prompt
            for k in range(0, len(extra or ''), 16):
                lf, rt = '', ''
                for c in extra[k:k + 16]:
                    o = ord(c)
                    lf += '%02x ' % o
                    if 32 <= o < 128:
                        rt += c
                    else:
                        rt += '.'

                print '    %-48s | %s' % (lf, rt)

        def __dictMerge(dicta, dictb, override=True):
            ret = dict()

            for key, value in dicta.items():
                ret[key] = value

            for key, value in dictb.items():
                if override:
                    ret[key] = value

            return ret

        def __objectReference(s, tp=0):
            if isinstance(s, (str, unicode)) and s.find('$') > -1:
                for k, val in enumerate(exported):
                    s = s.replace('$%d' % k, str(val))

                if tp & self._TYPE_EVAL:
                    try:
                        s = eval(s)
                    except:
                        pass

            return s

        def __bitsArray(vals, size, template=None):
            ret, offset = list(), 0

            for width in template or (1, ) * size:
                if offset < 8 * size:
                    mask = (1 << width) - 1
                    val = (vals >> offset) & mask
                    offset += width

                    ret.append(val)

            return ret

        def __convertTypeStrings(tps):
            tp = 0
            for ts in tps.split(','):
                tp |= self._TYPE_MAPS.get(ts.strip(), 0)

            return tp

        def __updateSubject(obj, subject_):
            subp, cont = subject_, ''
            if subject_ is not None and subject_.find(':') > 0:
                subp, cont = subject_.split(':', 1)

            subp = __objectReference(subp)
            cont = __objectReference(cont)
            obj.update(subject=subp, content=cont)

        ret = True
        total_nb = 0

        attr = kws.get('attr', kws.get('attribute'))
        objectp = AtrObject(plxname=self._getLastPlxName(), attribute=attr)
        for k, item in enumerate(descriptor):
            args = dict()
            val = val_str = ''
            subdescriptor = None

            tps = 'none'
            if len(item) == 2:
                subtitle, size = item
            elif len(item) == 3:
                subtitle, size, tps = item
            elif len(item) == 4:
                subtitle, size, tps, args = item
            else:
                raise ab_exceptions.AofException('<AtrObjectFormat.parse>',
                                                 'item %r has incorrect sizes'
                                                 % item)

            tp = __convertTypeStrings(tps)
            z = debug or ((tp & self._TYPE_DEBUG) == self._TYPE_DEBUG)

            args = __dictMerge(kws, args, True)
            if '_endian' not in args:
                args['_endian'] = self.endian

            repeat = args.get('repeat', 1)

            if tp & self._TYPE_EVAL:
                size = int(__objectReference(size, tp))
                repeat = int(__objectReference(repeat, tp))

            if z:
                __pt(z, '[Item %d] %s' % (k, str(item)))
                __pt(z, '  size=%d, repeat=%d' % (size, repeat))

            if size == 0 or tp & self._TYPE_IGNORE:
                off += size
                if z:
                    __pt(z, '  - loop continue')
                continue

            update_title = repeat > 1 and subtitle.find('%d') == -1

            title = subtitle
            func = args.get('func', args.get('function'))
            if func is not None:
                tp |= self._TYPE_FUNCTION

            for j in xrange(repeat):
                val, val_str = 0, ''
                if (tp & self._TYPE_VARIANT) != 0:
                    size, val = self.readVariableValue(afile, start + off)
                    val_str = data = "%d" % val
                else:
                    data = afile.read(size, start + off)

                if z:
                    __pt(z, '  DATA READ [offset: %d, size: %d, read: %d]' %
                         (off, size, len(data)), data)

                if update_title:
                    title = '%s#%d' % (subtitle, j + 1)

                if (tp & self._TYPE_FUNCTION) == self._TYPE_FUNCTION \
                        and func is not None:
                    try:
                        data = func(j, data, **kws)
                    except:
                        data = func(j, data)

                objp, exports = None, list()
                if tp & (self._TYPE_VALUE | self._TYPE_SHORT_VALUE |
                         self._TYPE_DECIMAL | self._TYPE_TIMESTAMP):
                    if isinstance(data, (int, long)):
                        val = data
                    else:
                        val = utils.atoi(data, **args)

                    if tp & self._TYPE_DECIMAL:
                        val_str = '%d' % val
                    elif tp & self._TYPE_TIMESTAMP:
                        val_str = time.ctime(val)
                    else:
                        if tp & self._TYPE_VALUE:
                            fmt = '0x%%0%dx' % (2 * size)
                        else:
                            fmt = '0x%x'

                        val_str = fmt % val
                elif tp & self._TYPE_STRING:
                    val = val_str = __objectReference(data, tp)
                elif tp & self._TYPE_BITS:
                    def __buildBitSub(le, o, s, v):
                        rep = '.' * o
                        for k in range(s):
                            rep += str((v >> (s - k - 1)) & 0x01)
                        rep += '.' * (le - o - s)

                        return rep

                    template = args.get('template', dict())
                    if template:
                        ars, length = list(), 0
                        for e in template or list():
                            ars.append(e[0])
                            length += e[0]

                        val = val = utils.atoi(data, **args)
                        vas = __bitsArray(val, size, template=ars)
                        objp = AtrObject(title, '',
                                         AtrField(start + off, size),
                                         plxname=self._getLastPlxName())

                        boff, desp = 0, list()
                        for k, v in enumerate(vas):
                            subsize, subtype, subtitle = template[k][:3]
                            sb = __convertTypeStrings(subtype)

                            __pt(z, 'subtype=%s, v=0x%02x' % (subtype, v))

                            ob = None
                            if v != 0 and (sb & self._TYPE_IGNORE) == 0:
                                ob = AtrObject(
                                    __buildBitSub(length, boff, subsize, v),
                                    field=AtrField(
                                        start + off + boff / 8,
                                        max(1, subsize / 8)),
                                    attribute=AtrAttribution.Simple)

                                if sb & self._TYPE_BOOLEAN:
                                    va = subtitle
                                elif sb & self._TYPE_ENUM:
                                    va = subtitle.get(v, 'UNKNOWN')
                                else:
                                    va = '0x%x' % v

                                desp.append(va)
                                ob.update(content='%s (0x%02x)' % (va, v))
                                objp.appendChildren(ob)

                            if sb & self._TYPE_EXPORT:
                                exports.append(v)

                            if sb & self._TYPE_EXPORT_OBJ:
                                exports.append(ob)

                            boff += subsize

                        val_str = ';'.join(desp)
                        if tp & self._TYPE_UPDATE_CONTENT:
                            objp.update(content=val_str)
                elif (tp & self._TYPE_VARIANT) != 0:
                    val = val_str = data

                __pt(z, '  title=%s, val=%s, val_str=%s' % (
                    title, str(val), val_str))
                if (tp & self._TYPE_NEED_VERIFY) != 0:
                    verified = args.get('verified', 0)
                    if tp & (self._TYPE_VALUE | self._TYPE_DECIMAL):
                        ret = val == verified
                    else:
                        ret = val_str == verified
                    __pt(z, '  verified=%s [verified=%s]' % (
                        ret, str(verified)))
                    if not ret:
                        break

                if (tp & self._TYPE_EXPORT) != 0:
                    exported.append(val)
                    __pt(z, '  exported added: ' + str(exported[-1]))

                if (not (len(val_str) == 0 and (
                        tp & self._TYPE_IGN_CONTENT))) and objp is None:
                    objp = AtrObject(
                        title, val if tp & self._TYPE_DECIMAL else val_str,
                        AtrField(start + off, size),
                        plxname=self._getLastPlxName())

                    subdescriptor = args.get('subdescriptor')
                    if subdescriptor:
                        ret, obj, _, num, exported = self._parse(
                            None, subdescriptor, afile, start, off, exported,
                            debug=z, **kws)

                        if ret and obj.hasChildren():
                            objp.appendChildren(obj.getChildren())
                            total_nb += num
                        else:
                            break

                if objp is not None:
                    if 'attr' in args or 'attribute' in args:
                        attr = args.get('attr', args.get('attribute'))
                        objp.update(attribute=AtrAttribution(attr))

                    if (tp & self._TYPE_EXPORT_OBJ):
                        exported.append(objp)
                        __pt(z, '  object exported added:' + str(objp))

                    if len(exports) > 0:
                        exported.extend(exports)
                        __pt(z, '  object exported added:' +
                             '\n    '.join([str(e) for e in exports]))

                    objectp.append(objp)
                    total_nb += 1

                off += size

            if not ret:
                break

        if ret:
            __updateSubject(objectp, subject)
            objectp.update(field=AtrField(start, off))

        return ret, objectp, off, total_nb, exported

    def parse(self, subject, descriptor, data=None, afile=None, start=0,
              offset=0, debug=False, **kws):
        if data is None and afile is None:
            raise ab_exceptions.AofException('<AtrObjectFormat.parse>',
                                             'both data and file are None')

        if data is not None:
            afile = ab_file.File(self.app, None, 'rb', data=data)

        # FIX: adjust the values of 'start' because here 'start' is the
        # file offset if the segment of ab_file is supported
        start += offset
        offset = 0

        ret, objectp, consumed, total, exported = self._parse(
            subject, descriptor, afile, start, offset, list(),
            debug=debug, **kws)

        return ret, objectp, consumed, total, exported
